/*
 * Copyright 2016-2019 yoara
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yhao.infra.web.common.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import yhao.infra.common.util.CommonMapUtil;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * http控制相关工具类
 *
 * @author yoara
 */
public class HttpClientUtil {
    private static Logger log = LoggerFactory.getLogger(HttpClientUtil.class);

    /**
     * Get或Post方法结果解析成String
     */
    public static String entityToString(HttpEntity entity) throws IOException {
        return EntityUtils.toString(entity);
    }

    /**
     * Get或Post方法结果解析成byte数组
     */
    public static byte[] entityToBytes(HttpEntity entity) throws IOException {
        return EntityUtils.toByteArray(entity);
    }

    /**
     * Get或Post方法结果用于下载
     */
    public static void entityForDownload(HttpEntity entity, HttpServletRequest request,
                                           HttpServletResponse response, String fileName,
                                           boolean closeOutputStream) throws IOException {
        InputStream stream = entity.getContent();
        // Remove blank in the filename, but do not use regex(\s) for some reason
        fileName = fileName.replace(" ", "");

        // get client agent info，and encode the fileName.(only in IE、FF、Chrome)
        String agent = request.getHeader("USER-AGENT");
        if (agent != null && agent.toUpperCase().indexOf("MSIE") != -1) { // IE
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } else {    // Other
            fileName = new String(fileName.getBytes(), "ISO-8859-1");
        }

        // output the data to client
        response.reset();
        response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.setContentType("application/octet-stream; charset=UTF-8");
        OutputStream out = null;
        try {
            out = response.getOutputStream();
            byte b[] = new byte[1024];
            int j;
            while ((j = stream.read(b)) != -1) {
                out.write(b, 0, j);
            }
            out.flush();
        } finally {
            if (closeOutputStream && out != null) {
                out.close();
            }
        }
    }

    public static HttpEntity httpRequest(HttpParam param){
        if(param.getMethod()==HttpMethod.POST){
            HttpPost post = initPost(param);
            return execute(post,param.isNeedSsl());
        }else{
            HttpGet get = initGet(param);
            return execute(get,param.isNeedSsl());
        }
    }

    public static HttpEntity get(String uri) {
        return httpRequest(HttpParam.makeGetHttpParam(uri));
    }

    public static HttpEntity getSsl(String uri) {
        HttpParam param = HttpParam.makeGetHttpParam(uri);
        param.setNeedSsl(true);
        return httpRequest(param);
    }

    public static HttpEntity get(String uri, String contentType) {
        HttpParam param = HttpParam.makeGetHttpParam(uri);
        param.setHeader(CommonMapUtil.NEW("Content-Type", contentType));
        return httpRequest(param);
    }

    public static HttpEntity getSsl(String uri, String contentType) {
        HttpParam param = HttpParam.makeGetHttpParam(uri);
        param.setHeader(CommonMapUtil.NEW("Content-Type", contentType));
        param.setNeedSsl(true);
        return httpRequest(param);
    }

    public static HttpEntity post(String uri,Object param){
        return post(uri,param,false);
    }

    public static HttpEntity post(String uri,Object param,boolean isSSL){
        HttpParam httpParam = HttpParam.makePostHttpParam(uri);
        httpParam.setParam(param);
        httpParam.setNeedSsl(isSSL);
        return httpRequest(httpParam);
    }

    private static HttpGet initGet(HttpParam param){
        HttpGet httpGet = new HttpGet(param.uri);
        if(param.getHeader()!=null&&param.getHeader().size()>0){
            for(Entry<String,String> entry:param.getHeader().entrySet()){
                httpGet.addHeader(entry.getKey(),entry.getValue());
            }
        }
        return httpGet;
    }

    private static HttpPost initPost(HttpParam param){
        HttpPost httppost = new HttpPost(param.getUri());
        List<NameValuePair> nvps = new ArrayList<>();
        if(param.getParam()!=null){
            JSONObject paramJson = (JSONObject)JSONObject.toJSON(param.getParam());
            for (Entry<String, Object> entry : paramJson.entrySet()) {
                Object value = entry.getValue();
                if(value==null){
                    continue;
                }
                nvps.add(new BasicNameValuePair(entry.getKey(), value != null ? value.toString() : null));
            }
        }
        if(param.getHeader()!=null&&param.getHeader().size()>0){
            for(Entry<String,String> entry:param.getHeader().entrySet()){
                httppost.addHeader(entry.getKey(),entry.getValue());
            }
        }
        httppost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
        return httppost;
    }

    private static HttpEntity execute(HttpUriRequest request,boolean isSSL) {
        HttpClient client = null;
        try {
            client = isSSL?new SSLClient():new DefaultHttpClient();
        } catch (Exception e) {
            log.error("HttpClientUtil ssl wrong:" + e.getMessage(),e);
            return null;
        }
        HttpResponse rsp;
        int status = 0;
        try {
            client.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 10000);
            client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
            rsp = client.execute(request);
            status = rsp.getStatusLine().getStatusCode();
            log.info("HttpClientUtil got status:" + status);
            if (status == 200) {
                return rsp.getEntity();
            }
        } catch (ClientProtocolException e) {
            log.error("HttpClientUtil found ClientProtocolException:" + e.getMessage(),e);
        } catch (IOException e) {
            log.error("HttpClientUtil found IOException:" + e.getMessage(),e);
        }finally {
            client.getConnectionManager().shutdown();
        }
        return null;
    }
    
    static class SSLClient extends DefaultHttpClient{
        public SSLClient() throws Exception{
            super();
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain,
                                               String authType) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] chain,
                                               String authType) throws CertificateException {
                }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = this.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();
            sr.register(new Scheme("https", 443, ssf));
        }
    }

    public static class HttpParam{
        private HttpMethod method;
        private Map<String,String> header;
        private boolean needSsl;
        private String uri;
        private Object param;

        private HttpParam(){}

        public static HttpParam makePostHttpParam(String uri){
            HttpParam param = new HttpParam();
            param.method = HttpMethod.POST;
            param.uri = uri;
            return param;
        }

        public static HttpParam makeGetHttpParam(String uri){
            HttpParam param = new HttpParam();
            param.method = HttpMethod.GET;
            param.uri = uri;
            return param;
        }

        public void setHeader(Map<String, String> header) {
            this.header = header;
        }

        public void setNeedSsl(boolean needSsl) {
            this.needSsl = needSsl;
        }

        public void setParam(Object param) {
            this.param = param;
        }

        public HttpMethod getMethod() {
            return method;
        }

        public Map<String, String> getHeader() {
            return header;
        }

        public boolean isNeedSsl() {
            return needSsl;
        }

        public Object getParam() {
            return param;
        }

        public String getUri() {
            return uri;
        }
    }
}
